package bitcask_go

import (
	"bitcask-go/data"
	"bitcask-go/index"
	"errors"
	"io"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
)

// DBEngine  bitcask 存储引擎实例
type DBEngine struct {
	options    Options
	activeFile *data.DataFile // 当前活跃数据文件，可以用于写入
	oldFiles   map[uint32]*data.DataFile
	fileIds    []int // 文件 id 集合，从小到大排序，只在加载索引时使用
	index      index.Indexer
	mu         *sync.RWMutex
}

func Open(options Options) (*DBEngine, error) {
	// 对用户传入的配置进行校验
	if err := checkOptions(options); err != nil {
		return nil, err
	}

	// 检查数据目录是否存在，如果不存在，则创建这个目录
	if _, err := os.Stat(options.DirPath); os.IsNotExist(err) {
		if err := os.MkdirAll(options.DirPath, os.ModeDir); err != nil {
			return nil, err
		}
	}

	// 初始化 DBEngine 实例结构体
	engine := &DBEngine{
		options:  options,
		oldFiles: make(map[uint32]*data.DataFile),
		index:    index.NewIndexer(options.IndexType),
		mu:       new(sync.RWMutex),
	}

	// 加载数据文件
	if err := engine.loadDataFile(); err != nil {
		return nil, err
	}

	// 从数据文件中加载索引
	if err := engine.loadIndexFromDataFiles(); err != nil {
		return nil, err
	}

	return engine, nil
}

// Put 写入 key/value 数据，key 不能为空
func (db *DBEngine) Put(key, value []byte) error {
	if len(key) == 0 {
		return ErrKeyIsEmpty
	}

	record := &data.LogRecord{
		Key:   key,
		Value: value,
		Type:  data.LogRecordNormal,
	}

	// 追加写入到当前活跃数据文件中
	pos, err := db.appendLogRecord(record)
	if err != nil {
		return err
	}

	if ok := db.index.Put(key, pos); !ok {
		return ErrIndexUpdateFailed
	}
	return nil
}

// Get 根据 key 读取数据
func (db *DBEngine) Get(key []byte) ([]byte, error) {
	db.mu.RLock()
	defer db.mu.RUnlock()

	if len(key) == 0 {
		return nil, ErrKeyIsEmpty
	}

	// 从内存的数据结构中取出 key 对应的索引信息
	pos := db.index.Get(key)
	if pos == nil {
		return nil, ErrKeyNotFound
	}

	// 根据文件 id 找到对应的数据文件
	var dataFile *data.DataFile
	if db.activeFile.FileId == pos.Fid {
		dataFile = db.activeFile
	} else {
		dataFile = db.oldFiles[pos.Fid]
	}

	if dataFile == nil {
		return nil, ErrDatFileNotFound
	}

	// 找到数据文件，根据偏移量读取数据
	record, _, err := dataFile.ReadLogRecord(pos.Offset)
	if err != nil {
		return nil, err
	}

	// 检查 key 是否被删除
	if record.Type == data.LogRecordDeleted {
		return nil, ErrKeyNotFound
	}

	return record.Value, nil
}

// Delete 删除指定 key 数据
// 实际是插入一个该 key 的墓碑值到数据文件中，
// 在后续的合并数据文件过程中才会真正删除
func (db *DBEngine) Delete(key []byte) error {
	db.mu.Lock()
	defer db.mu.Unlock()

	if len(key) == 0 {
		return ErrKeyIsEmpty
	}

	if pos := db.index.Get(key); pos == nil {
		return nil
	}

	record := &data.LogRecord{
		Key:  key,
		Type: data.LogRecordDeleted,
	}

	_, err := db.appendLogRecord(record)
	if err != nil {
		return err
	}

	if ok := db.index.Delete(key); !ok {
		return ErrIndexUpdateFailed
	}
	return nil
}

// 追加写数据到活跃文件中
func (db *DBEngine) appendLogRecord(record *data.LogRecord) (*data.LogRecordPos, error) {
	db.mu.Lock()
	defer db.mu.Unlock()

	if db.activeFile == nil {
		if err := db.setActiveDataFile(); err != nil {
			return nil, err
		}
	}

	// 写入编码后的数据
	encRecord, size := data.EncodeLogRecord(record)
	// 如果写入的数据以及到达了活跃文件的阈值，则关闭活跃文件，并打开新的文件
	if db.activeFile.WriteOffset+size > db.options.DataFileSize {
		if err := db.activeFile.Sync(); err != nil {
			return nil, err
		}

		// 当前活跃文件转换为旧的数据文件
		db.oldFiles[db.activeFile.FileId] = db.activeFile
		// 打开新的数据文件
		if err := db.setActiveDataFile(); err != nil {
			return nil, err
		}
	}

	writeOffset := db.activeFile.WriteOffset
	if err := db.activeFile.Write(encRecord); err != nil {
		return nil, err
	}

	if db.options.SyncWrites {
		if err := db.activeFile.Sync(); err != nil {
			return nil, err
		}
	}

	// 更新当前数据文件的写偏移量
	writeOffset += size

	// 构造内存索引信息
	pos := &data.LogRecordPos{
		Fid:    db.activeFile.FileId,
		Offset: writeOffset,
	}
	return pos, nil
}

// 设置当前活跃文件，调用该方法必须持有互斥锁
func (db *DBEngine) setActiveDataFile() error {
	var initialFileId uint32 = 0
	if db.activeFile != nil {
		initialFileId = db.activeFile.FileId + 1
	}

	// 打开新的数据文件
	dataFile, err := data.OpenDataFile(db.options.DirPath, initialFileId)
	if err != nil {
		return err
	}
	db.activeFile = dataFile
	return nil
}

// 从数据目录中加载数据文件
func (db *DBEngine) loadDataFile() error {
	dirEntries, err := os.ReadDir(db.options.DirPath)
	if err != nil {
		return err
	}

	var fileIds []int
	// 遍历以 .data 结尾的文件
	for _, entry := range dirEntries {
		if strings.HasSuffix(entry.Name(), data.DataFileNameSuffix) {
			fileId, err := strconv.Atoi(strings.Split(entry.Name(), ".")[0])
			// 数据目录可能被损坏了
			if err != nil {
				return ErrDataDirCorrupted
			}

			fileIds = append(fileIds, fileId)
		}
	}

	// 对文件 id 进行排序，从小到大依次加载
	sort.Ints(fileIds)

	db.fileIds = fileIds

	for i, fid := range fileIds {
		dataFile, err := data.OpenDataFile(db.options.DirPath, uint32(fid))
		if err != nil {
			return err
		}
		if i == len(fileIds)-1 { // 最后一个数据文件，当做活跃文件继续写入
			db.activeFile = dataFile
		} else {
			db.oldFiles[uint32(fid)] = dataFile
		}
	}
	return nil
}

// 从数据文件中加载索引
// 遍历文件中的所有记录，并更新到内存索引中
func (db *DBEngine) loadIndexFromDataFiles() error {
	// 空数据库，直接返回
	if len(db.fileIds) == 0 {
		return nil
	}

	for _, fid := range db.fileIds {
		var fileId = uint32(fid)
		var dataFile *data.DataFile
		if fileId == db.activeFile.FileId {
			dataFile = db.activeFile
		} else {
			dataFile = db.oldFiles[fileId]
		}

		var offset int64 = 0
		for {
			logRecord, size, err := dataFile.ReadLogRecord(offset)
			if err != nil {
				if err == io.EOF {
					break
				}
				return err
			}

			// 构建内存索引，并保存
			if logRecord.Type != data.LogRecordDeleted {
				pos := &data.LogRecordPos{
					Fid:    fileId,
					Offset: offset,
				}
				db.index.Put(logRecord.Key, pos)
			} else {
				db.index.Delete(logRecord.Key)
			}

			offset += size
		}

		// 如果是当前活跃文件，更新这个文件的 writeOffset
		if fileId == db.activeFile.FileId {
			db.activeFile.WriteOffset = offset
		}

	}
	return nil
}

func checkOptions(options Options) error {
	if options.DirPath == "" {
		return errors.New("database dir path is empty")
	}
	if options.DataFileSize <= 0 {
		return errors.New("database data file size must be greater than 0")
	}
	return nil
}
